package com.andova.component.net

import com.andova.*
import com.andova.ext.ad
import com.andova.ext.ae
import com.andova.ext.ai
import com.andova.ext.launchSuspend
import com.google.gson.JsonSyntaxException
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.BufferedSink
import okio.Okio
import retrofit2.Call
import retrofit2.Response
import java.io.File
import java.io.IOException
import java.net.ConnectException
import java.net.NoRouteToHostException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.net.ssl.SSLHandshakeException

class RetrofitSusCallback<T : ResponseModel> : retrofit2.Callback<T>, NetworkSusCall {
    private var mCall: Call<T>? = null
    private var mCallback: NetworkSusCallback<T>? = null
    override fun onResponse(call: Call<T>, response: Response<T>) = launchSuspend {
        mCallback ?: return@launchSuspend
        try {
            mCallback?.response(call, response)
            val body = response.body()
            if (!response.isSuccessful || body == null || !body.isSuccess) {
                mCallback?.error(response)
                mCallback?.complete(ERROR_URL2SERVER)
                return@launchSuspend
            }
            if (body.isEmpty) {
                mCallback?.complete(RESPONSE_EMPTY)
                return@launchSuspend
            }
            mCallback?.success(response.body()!!)
            mCallback?.complete(RESPONSE_SUCCESS)
        } catch (e: Exception) {
            onFailure(call, e)
        }
    }

    override fun onFailure(call: Call<T>?, throwable: Throwable) = launchSuspend {
        mCallback ?: return@launchSuspend
        mCallback?.failure(throwable, call)
        when (throwable) {
            is JsonSyntaxException -> mCallback?.complete(ERROR_JSON_SYNTAX)
            is SocketTimeoutException -> mCallback?.complete(ERROR_TIMEOUT)
            is ConnectException -> mCallback?.complete(ERROR_CONNECT)
            is SSLHandshakeException -> mCallback?.complete(ERROR_SSL_HANDSHAKE)
            is NoRouteToHostException -> mCallback?.complete(ERROR_NO_ROUTE_TO_HOST)
            is UnknownHostException -> mCallback?.complete(ERROR_UNKNOWN_HOST)
            is IllegalArgumentException -> mCallback?.complete(ERROR_ILLEGAL_ARGUMENT)
            else -> mCallback?.complete(ERROR_UNKNOWN)
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun request(callback: NetworkSusCallback<out ResponseModel>?, call: Any?) {
        // TODO: 2019-02-12 网络变化情况
        mCall = call as? Call<T>
        mCallback = callback as? NetworkSusCallback<T>
        launchSuspend { mCallback?.prepare() }
        try {
            // todo fix ‘java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again’
            mCall?.enqueue(this)
        } catch (ex: Exception) {
            onFailure(mCall, ex)
        }
    }

    override fun download(url: String, path: String, callback: (msg: Any) -> Unit) = download(url, File(path), callback)

    @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
    override fun download(url: String, dest: File, callback: (msg: Any) -> Unit) {
        val startTime = System.currentTimeMillis()
        ad("ready download $url")
        val request: Request = Request.Builder().url(url).build()
        OkHttpClient().newCall(request).enqueue(object : Callback {
            override fun onFailure(call: okhttp3.Call, e: IOException) {
                ae("download failed: $e")
                callback(e)
            }

            override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
                var bufferedSink: BufferedSink? = null
                try {
                    bufferedSink = Okio.buffer(Okio.sink(dest))
                    bufferedSink.writeAll(response.body()?.source())
                    bufferedSink.close()
                    ai("download success, it take ${System.currentTimeMillis() - startTime}ms")
                    callback(dest)
                } catch (e: Exception) {
                    ae("download exception: $e")
                    callback(e)
                } finally {
                    bufferedSink?.close()
                }
            }
        })
    }

    override fun version(): String = "2.4.0"
}